fix(gke): plumb UseSSL through GKE Autopilot deploy path#302
Conversation
Semgrep Scan ResultsRepository:
Scanned at 2026-05-30 08:13 UTC |
Security Scan ResultsRepository:
Scanned at 2026-05-30 09:55 UTC |
End-to-end verification on a preview build — PASSEDBuilt SC preview Before/after the Service annotationPre-fix (live state of a long-running GKE Autopilot deploy, queried via Post-fix (same query, after deploying via the preview SC):
Ready to merge. |
The GKE Autopilot cloud-compose deploy path in
`pkg/clouds/pulumi/gcp/gke_autopilot_stack.go` was building a
`kubernetes.Args{}` struct without setting `UseSSL`, so it landed at
the Go bool zero value (`false`). The per-stack Caddyfile rendering in
`simple_container.go:707-708` then never emitted `import hsts`:
if args.UseSSL {
imports = append(imports, "import hsts")
}
Effect: every GKE Autopilot deploy silently omitted `import hsts` from
its rendered Caddyfile entry, so the parent Caddy's `(hsts)` snippet
never fired — no HSTS site-level header, no HTTP→HTTPS redirect — even
for stacks declaring `lbConfig.https: true` in their stack yaml.
The Cloudrun deploy path (`kube_run.go:102`) was already correctly
setting `args.UseSSL`, so this is a GKE-specific asymmetry.
Verified by reading a live K8s Service annotation pre-fix: `import gzip`
+ `import handle_static` were present, `import hsts` missing. After
fix (validated via a preview build of this branch), `import hsts`
appears on the same Service.
Fix: derive `useSSL` from the consumer's `lbConfig.https` setting
(the existing per-stack yaml field that already declares "this stack
is HTTPS-fronted") and pass it to `kubernetes.Args.UseSSL`. Same field
also determines the site-block protocol in the same rendering pass
(`proto = lo.If(lbConfig.Https, "https").Else("http")`), so the two
signals stay aligned. Stacks not setting `lbConfig.https: true`
continue to get no HSTS — matching today's behaviour exactly for the
non-HTTPS case.
No new schema fields; no new template-level knobs. One signal, one
behaviour: declare HTTPS at the consumer level, get HSTS automatically.
Two SC outputs gated by `args.UseSSL` in the kubernetes.Args contract
that this PR therefore turns on for HTTPS GKE stacks:
1. `import hsts` in the per-stack Caddyfile entry → the parent Caddy's
`(hsts)` snippet sets Strict-Transport-Security site-level (covers
ALL response paths including static assets + error pages, not just
proxied responses) AND redirects HTTP→HTTPS on
X-Forwarded-Proto: http.
2. `ingress.kubernetes.io/ssl-redirect: false` annotation on the
Ingress (Caddy owns the redirect, so the ingress shouldn't).
Both already correctly fire on the Cloudrun path; this fix brings GKE
to parity.
No tests added: the existing `TestCaddyfileEntry_*` suite covers the
rendering once `args.UseSSL` is true. The derivation itself is a
single expression too small to need its own unit test — end-to-end via
SC preview pipeline is the meaningful verification.
Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
71077b9 to
8203458
Compare
End-to-end verification of the simplified fix — PASSEDBuilt SC preview Rendered
|
Summary
The GKE Autopilot cloud-compose deploy path silently omits
import hstsfrom every per-stack Caddyfile entry becausekubernetes.Args.UseSSLis never set — it lands at the Go bool zero value (false), andsimple_container.gogatesimport hstsonargs.UseSSL. Result: the parent Caddy's(hsts)snippet never fires for GKE Autopilot deploys, no matter whatlbConfig.httpsthe consumer declares.Asymmetric with the Cloudrun deploy path (
kube_run.go:102), which already wiresargs.UseSSLcorrectly.Purpose
Make HSTS work out of the box on the GKE Autopilot deploy path for any stack that declares it's HTTPS-fronted. Today, consumers who set
lbConfig.https: truein their stack yaml get thehttps://site-block protocol (and TLS termination on Caddy), but not the correspondingimport hsts— they have to addheader_down Strict-Transport-Securitymanually vialbConfig.extraHelpersto get any HSTS, and even then it only fires on proxied responses (not static assets / error pages).After this fix,
lbConfig.https: trueis the single user-facing signal that turns on both the HTTPS site block and the HSTS snippet — same intent, same knob.Root cause
kubernetes.Argsinpkg/clouds/pulumi/gcp/gke_autopilot_stack.gowas constructed withoutUseSSL:if args.UseSSL { imports = append(imports, "import hsts") }→ never executes for GKE →(hsts)snippet inert → no HSTS header on responses.Verified by reading a live Service annotation:
import gzip+import handle_staticpresent,import hstsmissing.Fix
In
gke_autopilot_stack.go, deriveuseSSLfrom the consumer'slbConfig.httpssetting (the same per-stack yaml field that already controls the site-block protocol in the same rendering pass) and pass it through tokubernetes.Args:One signal in, two aligned outputs (site-block protocol +
import hsts).Approach choice
Considered adding a separate
UseSSL *boolfield onGkeAutopilotTemplate(mirrorCloudrunTemplate.UseSSLexactly). Rejected because:lbConfig.httpsis already the consumer-facing declaration of HTTPS intent — adding a second knob at the template level would duplicate the signal.The CloudrunTemplate path keeps its independent
UseSSLknob for now (out of scope for this fix); future work could harmonize, but no behaviour change there.Compatibility
lbConfig.https: truenow getimport hstsautomatically. The parent(hsts)snippet (inembed/caddy/Caddyfile) setsStrict-Transport-Security: max-age=31536000; includeSubDomains; preloadsite-level and redirects HTTP→HTTPS onX-Forwarded-Proto: http.lbConfig.https: truecontinue to get no HSTS — byte-identical behaviour to today's broken state. No silent default flip; no risk to plain-HTTP GKE deployments.(hsts)snippet'srediris gated onX-Forwarded-Proto: http(matched only when an upstream proxy sets it), so HTTP-only origins that aren't fronted by such a proxy see no redirect loop. The always-on Strict-Transport-Security header is the sticky bit — only fires on stacks that explicitly opted into HTTPS vialbConfig.https: true, which is exactly the right consent model.Test plan
pkg/clouds/pulumi/kubernetes/...).branch-preview.yaml) tagged — see linked verification comment.caddyfile-entryannotation on the K8s Service now containsimport hstsalongsideimport gzip+import handle_static. Pre-fix that line was missing on the same Service.Reviewer feedback addressed
Original revision of this PR added a
UseSSL *boolfield toGkeAutopilotTemplate(mirroring CloudrunTemplate). Reviewer feedback flagged this as adding unnecessary API surface whenlbConfig.httpsalready carried the HTTPS-intent signal. This revision drops the new field and derives directly from the existing per-stack flag.